http://bit.ly/jmb-cpoa http://iut-blagnac.github.io/cpoa/
Jean-Michel Bruel jbruel@gmail.com
Ce cours porte sur la Conception et Programmation Objet Avancée.
Vous avez appris (cf. R2.03 et R2.04) un certain nombre de concepts objets :
Abstraction
Encapsulation
Héritage
Polymorphisme
Définition :
Définition (restrictive) :
Une classe est une abstraction des caractéristiques communes d’un ensemble d’objets.
Définition :
Définition (restrictive) :
Dans la description d’un objet, le but de l'encapsulation est de masquer les attributs et les méthodes, c’est-à-dire, la manière dont est réalisé le comportement de l’objet.
Définition :
Définition (simpliste) :
L’héritage est la transmission de caractéristiques à ses descendants.
La classe qui hérite dispose des méthodes et attributs de niveau public et
protected de sa classe mère.
Le nom de polymorphisme vient du grec :
qui peut prendre plusieurs formes
L’héritage concerne les classes, le polymorphisme concerne les objets. |
On distingue généralement trois types de polymorphisme :
Le polymorphisme ad hoc (également surcharge ou en anglais overloading)
Le polymorphisme paramétrique (également généricité ou en anglais template)
Le polymorphisme d’héritage (également redéfinition, spécialisation ou en anglais overriding)
Appelé aussi surcharge.
Permet d’avoir des fonctions de même nom dans des classes sans aucun rapport entre elles.
Permet de définir des opérateurs d’utilisation différente en fonction des paramètres.
int method(int,int);
int method(int);
int method(float,float);Appelé aussi généricité.
interface Iterator<E> {
boolean hasNext();
E next();
}
public <T> static void copy(Collection<? extends T> source, Collection<? super T> dest) {
for (T t : source) {
dest.add(t);
}
}Appelé aussi spécialisation (ou redéfinition).
Lié à la redéfinition des méthodes héritées.
Eviter trois problèmes principaux :
Anticiper les évolutions
Eviter les erreurs dues aux modifications
Rendre plus facile la réutilisation
Pour répondre aux problèmes ci-dessus, on va s’attaquer à diminuer les dépendances et éviter l'"effet spaghetti".
Les qualités recherchées sont :
Robustesse : les changements n’introduisent pas de régressions.
Extensibilité : il est facile d’ajouter de nouvelles fonctionnalités.
Réutilisabilité : il est possible de réutiliser certaines parties de code pour construire d’autres applications.
Nous allons apprendre des bonnes pratiques :
Identifier les aspects qui varient et les séparer des aspects constants
Programmer une interface, non une implémentation
Préférer la composition à l’héritage
Nous allons apprendre des bonnes pratiques :
Les classes doivent être ouvertes à l’extension, mais fermées à la modification
Dépendez d’abstractions. Ne dépendez pas de classes concrètes (inversion des dépendances)
Ne parlez pas aux inconnus
L’étape suivante consiste à apprendre les bonnes solutions de conception, ce qu’on appelle les patrons de conception (ou design patterns en anglais).
Le fait d’attribuer un type (une classe) à une variable (un objet) peut se faire de plusieurs façons :
statique
dynamique
duck typing
On parle de typage statique quand la majorité des vérifications de type sont effectuées au moment de la compilation.
int i = 0; // cette déclaration indique explicitement que
// la variable i est de type entierLe typage dynamique consiste à laisser l’ordinateur réaliser l’opération de typage à la volée, lors de l’exécution du code.
/**
* @author André Peninou
*/
public class Type {
void m() {
System.out.println ("Type");
}
}
public class SousType extends Type {
void m() {
System.out.println ("SousType");
}
void autreM(){
System.out.println ("Spécifique SousType");
}
}
...
Type a = new Type();
a.m(); // "Type"
a = new SousType();
a.m(); // "SousType"
// Statique : a est un Type (à la compil)
// Dynamique : a est un SousType au runtime.
// D'où :
a = new SousType();
a.autreM();
// NOK car type statique == A => autreM() n'existe pas à la compilation
...Style de typage dynamique où la sémantique d’un objet (c’est-à-dire son type) est déterminée par l’ensemble de ses méthodes et de ses attributs, et non par un type défini et nommé explicitement par le programmeur.
L’origine de cette expression est liée à cette citation :
— James Whitcomb Riley |
def calcule(a, b, c)
return a*b+c
end
$a = calcule(6, 3, 2)
$b = calcule('6', 3, ', the number of the beast')
puts $a.to_s
puts $b.to_sCe qui donne :
20 666, the number of the beast
| Pour aller plus loin : http://fr.wikipedia.org/wiki/Duck_typing |
Science is what we understand well enough to explain to a computer. Art is everything else we do.
Principe de conception
|
Principe de conception
|
Principe de conception
|
Design pattern : Stratégie (Strategy) Stratégie définit une famille d’algorithmes, encapsule chacun d’eux et les rend interchangeables. Il permet à l’algorithme de varier indépendamment des clients qui l’utilisent. |
| L’exemple qui suit est tiré de ce cours. |
Le problème
Vous avez une classe FileWriter qui a pour rôle d’écrire dans un fichier
ainsi qu’une classe DBWriter. Dans un premier temps, ces classes ne
contiennent qu’une méthode write() qui n’écrira que le texte passé
en paramètre.
Au fil du temps, vous vous rendez compte que c’est dommage qu’elles ne fassent que ça et vous aimeriez bien qu’elles puissent écrire en différents formats (HTML, XML, etc.) : les classes doivent donc formater puis écrire.
L’interface en PHP (code source ici) |
La classe abstraite Writer (code source ici) |
La classe FileWriter (code source ici) |
La classe DBWriter (code source ici) |
Enfin, nous avons nos trois formateurs.
L’un ne fait rien de particulier (TextFormater),
et les deux autres formatent le texte en deux langages
différents (HTMLFormater et XMLFormater).
La classe TextFormater (code source ici) |
La classe HTMLFormater (code source ici) |
La classe XMLFormater (code source ici) |
La fonction standard sort() de python
>>> sorted("This is a test string from Andrew".split(), key=str.lower)
['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']Stratégie de cryptage en fonction de la taille d’un fichier
File file = getFile();
Cipher c = CipherFactory.getCipher( file.size() );
c.performAction();
// implementations:
interface Cipher {
public void performAction();
}
class InMemoryCipherStrategy implements Cipher {
public void performAction() {
// load in byte[] ....
}
}
class SwaptToDiskCipher implements Cipher {
public void performAction() {
// swapt partial results to file.
}
}| Plus de détails ici |
| Les patrons ne sont pas réutilisables! |
Il faut implémeter la solution qu’il représente à chaque fois.
Exception : certains font l’objet d’une librairie (comme Observer de Java).
Par exemple le patron Singleton existe dans la bibliothèque standard du langage en Ruby. C’est un mixin qu’il suffit d’inclure dans la classe qui doit être un singleton.
class Klass
include Singleton
# ...
end
a,b = Klass.instance, Klass.instance
a == b
# => true
Klass.new
# => NoMethodError - new is private ...Et donc deux implémentations :
public class Colvert extends Canard {
protected Colvert() {
this(new VolerAvecDesAiles(), new Cancan());
}
...
c1 = new Colvert();...
vol = new VolerAvecDesAiles();
cri = new Cancan();
c1 = new Colvert(vol,cri);
...Alexander : patterns pour les architectures (les vraies)
Beck et Cunningham : patterns pour des interfaces utilisateurs
Meyer : livre sur l’orienté objet (langage Eiffel), devenu la bible pour beaucoup de programmeurs (cf. [Meyer88])
Gamma, Helm, Johnson et Vlissides : LE livre de référence (cf. [GoF])
| Les auteurs de ce livre sont connus comme les Gof pour « Gang of Four ». |
Martin : principes SOLID (cf. [Martin03])
Craig Larman décrit des modèles de conception : les Patterns GRASP (cf. [Larman05])
SOLID:
Single Responsibility Principle
Open-Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Responsabilité => Sujet à changement
Ouvert à l'extension mais fermé à la modification
Ainsi, une fois écrite et testée, une classe ne devrait être modifiée que pour être corrigée! Toute modification devrait être possible par extension.
Une classe doit pouvoir être remplacée par une instance d'un de ses sous-types, sans modifier la cohérence du programme
Un carré est un rectangle particulier.
Question Peut-on toujours substituer un Carré à la place d’un Rectangle ? |
Rectangle.java)class Rectangle
{
protected int m_width;
protected int m_height;
public void setWidth(int width){
m_width = width;
}
public void setHeight(int height){
m_height = height;
}
public int getWidth(){
return m_width;
}
public int getHeight(){
return m_height;
}
public int getArea(){
return m_width * m_height;
}
}Square.java)// Violation of Likov's Substitution Principle
class Square extends Rectangle
{
public void setWidth(int width){
m_width = width;
m_height = width;
}
public void setHeight(int height){
m_width = height;
m_height = height;
}
}Square.java - suite)class LspTest
{
private static Rectangle getNewRectangle()
{
// it can be an object returned by some factory ...
return new Square();
}
public static void main (String args[])
{
Rectangle r = LspTest.getNewRectangle();
r.setWidth(5);
r.setHeight(10);
// User knows that r is a rectangle.
// It assumes that he's able to set the width and height as for the base class
System.out.println(r.getArea());
// Now she's surprised to see that the area is 100 instead of 50.
}
}Rectangle.java)class LspTest
{
private static Square getNewSquare()
{
// it can be an object returned by some factory ...
return new Rectangle();
}
public static void main (String args[])
{
Square s = LspTest.getNewSquare();
s.setWidth(5);
// User knows that r is a rectangle.
// It assumes that he's able to set the width and height as for the base class
System.out.println(s.getArea());
// Now she's surprised to see that the area is 0 instead of 25.
}
}Préférer plusieurs interfaces spécifiques pour chaque client plutôt qu'une seule interface générale
Il faut dépendre des abstractions, pas des implémentations
Ce principe indique :
Les modules de haut niveau (abstraits) ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d’abstractions.
Les abstractions ne doivent pas dépendre des détails d’implémentation. C’est l’inverse : les détails doivent dépendre des abstractions.
| Ainsi ce principe va à l’encontre de l’intuition classique. |
QUESTION Lesquels des 5 principes SOLID s’appliquent bien à Strategy ? |
The critical design tool for software development is a mind well educated in design principles. It is not the UML or any other technology.
Il s’agit d’un ensemble de patrons, plutôt orientés conception (UML). Nous en aborderons certains au travers des exemples de ce module (cf. [Larman05]).
| Notez que les principes SOLID ne s’appliquent pas qu’à la programmation objet. Pour une discussion sur leur application avec React (language fonctionnel), cf. https://dev.to/shadid12/can-you-apply-solid-principles-to-your-react-applications-46il. |
Réponses éprouvées à des problèmes récurrents
Vocabulaire commun
T’as qu’à utiliser une factory!
Décorateur
Commande
Façade
Patron de méthode
Chaînes de responsabilité
Prototype
Mémento
Médiateur
Interprète
Poids-mouche
Monteur
Pont
Document réalisé par Jean-Michel Bruel via Asciidoctor (version 2.0.23) de 'Dan Allen', lui même basé sur AsciiDoc.
Pour l’instant ce document est libre d’utilisation et géré par la 'Licence Creative Commons'.
licence Creative Commons Paternité - Partage à l'Identique 3.0 non transposé.